use crate::chunk::Chunk;
use crate::container::{ContainerStore,CAP_INIT_SIZE, ContainerError, ContainerWriteItem};
use crate::index::IndexStore;
//use crate::recipe::FileInfo;
use crate::recipe::{RecipeWriter, VersionControlBlock};
use crate::recipe::ChunkPointer;
use crate::constants::{
	CONTAINER_FILE_NAME, INDEX_DIR_NAME, VERSION_DIR_NAME,
};

use super::{BackupStatistics, BackupResult, BackupConfig};
use super::BackupError;
use super::delta::EncoderProduct;
use std::fmt::Debug;
use std::fs::create_dir_all;
use std::path::Path;
use pipelines::Receiver;
use std::sync::Arc;
use parking_lot::{Mutex,Condvar};

pub struct ChunkWriter{
	path:String,
	version:Option<String>,
	container_store:Arc<ContainerStore>,
	index_store:Arc<IndexStore>,
	wait_write_all:(Condvar,Mutex<bool>),
	statistics:Arc<Mutex<BackupStatistics>>,
}

impl ChunkWriter {
	pub fn from_path<P: AsRef<Path>+?Sized>(path:&P,sf_num:usize,version:Option<String>,statistics:Arc<Mutex<BackupStatistics>>,config:&BackupConfig) -> BackupResult<Self> {
		let path = path.as_ref();
		if let Err(e) = create_dir_all(path) { panic!("{}",e); }
		Ok(Self {
			path:path.to_str().unwrap().to_string(),
			version:version,
			container_store:Arc::new(ContainerStore::from_path(&path.join(CONTAINER_FILE_NAME),config.find_in_recent_buffer)?),
			index_store:Arc::new(IndexStore::from_path(&path.join(INDEX_DIR_NAME),sf_num)?),
			statistics:statistics,
			wait_write_all:(Condvar::new(),Mutex::new(false)),
		})
	}

	pub fn wait_write_all(&self) {
		let mut write_all = self.wait_write_all.1.lock();
		if !*write_all {
			self.wait_write_all.0.wait(&mut write_all);
		}
	}	

	pub fn set_write_all(&self) {
		let mut write_all = self.wait_write_all.1.lock();
		*write_all = true;
		self.wait_write_all.0.notify_all();
	}

	pub fn write_async(&self) -> BackupResult<()> {
		let result = self.container_store.write();
		self.container_store.set_write_all();
		Ok(result?)
	}

	pub fn container_store(&self) -> Arc<ContainerStore> {
		self.container_store.clone()
	}

	pub fn index_store(&self) -> Arc<IndexStore> {
		self.index_store.clone()
	}

	pub fn write<T:ChunkPointer+Debug+Send>(&self,recv:Receiver<EncoderProduct<T>>) 
										-> Result<(),BackupError> {
		let path:&Path = self.path.as_ref();
		let vcb = VersionControlBlock::from_path(&path.join(VERSION_DIR_NAME))?;
		let recipe_dir_name = vcb.new_version(self.version.clone())?;
		let mut recipe_writer = RecipeWriter::<T>::from_path(
			&<String as AsRef<Path>>::as_ref(&self.path).join(recipe_dir_name)
		)?;
		let half_container_info = vcb.get_half_container_info()?;
		let mut new_container = self.container_store.container_alloc_with_info(half_container_info);
		let mut after_dedup_len:usize = 0;
		let mut after_delta_len:usize = 0;
		for c in recv {
			match c {
				EncoderProduct::FileInfo(info) => {
					recipe_writer.append_file(&info)?;
				}
				EncoderProduct::Chunk(c) => {
					if let Some(cp) = c.get_chunk_pointer() {
						recipe_writer.append_chunk_pointer(&cp)?;
						continue;
					}
					after_dedup_len += c.as_unique_len();
					after_delta_len += <Chunk as ContainerWriteItem>::len(&c);
					for try_append in 0..2 {
						match Arc::get_mut(&mut new_container).unwrap().append_item(c.clone()) {
							Ok(cp) => {
								recipe_writer.append_chunk_pointer(&cp)?;
								break;
							},
							Err(ContainerError::NotEnoughSpace(_)) => {
								assert_eq!(try_append,0);
								self.container_store.write_async(new_container.clone(),&self.index_store)?;
								new_container = self.container_store.container_alloc();
							}
							Err(e) => {
								return Err(e.into());
							}
						}
					}
				}
				EncoderProduct::ChunkPointer(cp) => {
					recipe_writer.append_chunk_pointer(&cp)?;
				}
			}
		}
		if new_container.cap() != CAP_INIT_SIZE {
			vcb.keep_half_container_info(&new_container)?;
			self.container_store.write_async(new_container.clone(),&self.index_store)?;
		}
		vcb.flush_all()?;
		self.container_store.set_no_more();
		self.container_store.wait_write_all();
		let mut statistics = self.statistics.lock();
		statistics.set_write_phase_time(self.container_store.get_write_time());
		statistics.set_after_dedup_len(after_dedup_len);
		statistics.set_after_delta_len(after_delta_len);
		Ok(())
	}
}

